package edu.cornell.cs.lsi.mapreduce.pass1;

public class DoublePoint {

    private double x;
    private double y;
    private DoublePoint parent;

    public DoublePoint() {
        x = 0;
        y = 0;
        parent = this;
    }

    public DoublePoint(double x, double y) {
        this.x = x;
        this.y = y;
        parent = this;
    }

    public DoublePoint(DoublePoint p) {
        this.x = p.x;
        this.y = p.y;
        parent = this;
    }

    public static DoublePoint parseFromString(String s) {
        String[] split = s.split(" ");
        DoublePoint result = new DoublePoint();
        result.x = Double.parseDouble(split[0]);
        result.y = Double.parseDouble(split[1]);
        result.parent = result;

        return result;
    }

    public void union(DoublePoint other) {

        DoublePoint newParent;
        DoublePoint curPoint;
        DoublePoint nextPoint;

        DoublePoint myOldestParent = this.parent;
        DoublePoint otherOldestParent = other.parent;

        // the reason that I don't just call find() here is that
        // I want to preserve the chains of parentage so
        // I can update all nodes in the chain that has to change
        // If I call find(), the only nodes I will be able to update are the ones
        // At the beginning and the end

        while (myOldestParent.parent != myOldestParent) {
            myOldestParent = myOldestParent.parent;
        }
        while (otherOldestParent.parent != otherOldestParent) {
            otherOldestParent = otherOldestParent.parent;
        }

        if (myOldestParent.isLessThanX(otherOldestParent)) {
            newParent = myOldestParent;
        } else {
            newParent = otherOldestParent;
        }

        curPoint = this;
        while (curPoint != newParent) {
            nextPoint = curPoint.parent;
            curPoint.parent = newParent;
            curPoint = nextPoint;
        }
        curPoint = other;
        while (curPoint != newParent) {
            nextPoint = curPoint.parent;
            curPoint.parent = newParent;
            curPoint = nextPoint;
        }

    }

    public DoublePoint find() {
        DoublePoint newParent = this.parent;
        DoublePoint curPoint = this;
        DoublePoint nextPoint;

        // find the oldest parent
        while (newParent.parent != newParent) {
            newParent = newParent.parent;
        }

        // update all nodes along the path
        // this will halt when the oldest parent is reached
        while (curPoint != newParent) {
            nextPoint = curPoint.parent;
            curPoint.parent = newParent;
            curPoint = nextPoint;
        }
        this.parent = newParent;

        return newParent;
    }

    public String toString() {
        return x + " " + y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public boolean isLessThanX(DoublePoint other) {
        if (x < other.x)
            return true;
        if (x > other.x)
            return false;
        if (y < other.y)
            return true;
        if (y > other.y)
            return false;
        return false; // this is not supposed to happen.
        // the dataset is not supposed to contain duplicate points
    }

    public boolean isLessThanY(DoublePoint other) {
        if (y < other.y)
            return true;
        if (y > other.y)
            return false;
        if (x < other.x)
            return true;
        if (x > other.x)
            return false;
        return false; // this is not supposed to happen.
        // the dataset is not supposed to contain duplicate points
    }

    public boolean equals(Object ob) {
        DoublePoint other;

        if (ob == null)
            return false;
        if (!(ob instanceof DoublePoint))
            return false;
        other = (DoublePoint) ob;
        return y == other.y && x == other.x;

    }

    public boolean withinRange(DoublePoint other, double rangeSquared) {
        return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y) <= rangeSquared;
    }

    @Override
    public int hashCode() {
        return new Double(x).hashCode() ^ new Double(y).hashCode();
    }

    public String toStringSelfAndParent() {
        find();
        return x + " " + y + " " + parent.x + " " + parent.y;
    }

}
